#pragma once

#include <iostream>
#include <string>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>

#include "../common/Util.hpp"
#include "../common/Log.hpp"
namespace ns_run{

    /*
    * 对于OJ而言，一般不关心运行结果如何，只关注运行成功与否
    * 可执行程序需要有标准输入输出错误，那么后台执行与前台显然无法直接链接
    * 因此我们需要单独定义三个零食文件以供标准输入输出
    */
    class Runner{
       
        public:
        //KB为单位
        static void SetLimitResource(rlim64_t cpulimit, rlim64_t memlimit)
        {
            rlim64_t KB = 1024; 
            struct rlimit64 rlim;
            rlim.rlim_cur = cpulimit;
            rlim.rlim_max = cpulimit*2;
            setrlimit64(RLIMIT_CPU,&rlim);

            rlim.rlim_cur = memlimit*KB;
            rlim.rlim_max = memlimit*KB*2;
            setrlimit64(RLIMIT_AS,&rlim);
        }

        /*返回值如果时0，正常运行结束，但是没对结果做出判断，其具体结果保存到，filename.stderr,filename.stdin,filename.stdout
        *如果返回值大于0，说明是收到了信号
        *返回值小于0，则是说明内部程序出错，可能是系统调用出错误
        * cpu_limit是该程序需要的CPU时间限制,秒
        * memory_limit 是该程序所需要的内存限制，KB
        */
        static int Run(const std::string &file_name,rlim64_t cpu_limit,rlim64_t memory_limit)
        {
            std::string execute = ns_Util::TempPathUtil::FormExe(file_name);
            std::string temp_stderr = ns_Util::TempPathUtil::StderrPath(file_name);
            std::string temp_stdout = ns_Util::TempPathUtil::StdoutPath(file_name);
            std::string temp_stdin = ns_Util::TempPathUtil::StdinPath(file_name);
            umask(0);
            int stdin_fd = open(temp_stdin.c_str(),O_RDONLY ,0644);
            int stderr_fd = open(temp_stderr.c_str(),O_CREAT |O_TRUNC |O_WRONLY ,0644);
            int stdout_fd = open(temp_stdout.c_str(),O_CREAT |O_TRUNC |O_WRONLY ,0644);
            if(stderr_fd<0 || stdin_fd<0 || stdout_fd<0)
            {
                //文件打开失败
                std::cerr<<ns_Log::lg(ns_Log::Error,__FILE__,__LINE__)<<"文件描述符创建错误\n";
                return -1;
            }
            
            pid_t forkid = fork();
            if(forkid<0)
            {
                close(stdin_fd);
                close(stderr_fd);
                close(stdout_fd);
                std::cerr<<ns_Log::lg(ns_Log::Error,__FILE__,__LINE__)<<"子进程创建错误\n";
                return -2;//创建失败
            }
            else if(forkid ==0)
            {
                dup2(stdin_fd,stdin->_fileno);
                dup2(stdout_fd,stdout->_fileno);
                dup2(stderr_fd,stderr->_fileno);
                SetLimitResource(cpu_limit,memory_limit);
                execl(execute.c_str(),execute.c_str(),nullptr);   std::cerr<<ns_Log::lg(ns_Log::Error,__FILE__,__LINE__)<<"进程替换错误\n";
                
                exit(-1);
            }
            else
            {
                close(stdin_fd);
                close(stderr_fd);
                close(stdout_fd);
                int SigCode;
                waitpid(forkid,&SigCode,0);

                //返回信号
                std::cerr<<ns_Log::lg(ns_Log::Info,__FILE__,__LINE__)<<"运行完毕"<<"signal:"<<WTERMSIG(SigCode)<<"\n";
                return WTERMSIG(SigCode);
            }
        }
    };
};